#include "ChaperoneTabController.h"
#include <QQuickWindow>
#include "../overlaycontroller.h"

// application namespace
namespace advsettings {


void ChaperoneTabController::initStage1() {
	m_height = getBoundsMaxY();

	auto settings = OverlayController::appSettings();
	settings->beginGroup("chaperoneSettings");
	m_enableChaperoneSwitchToBeginner = settings->value("chaperoneSwitchToBeginnerEnabled", false).toBool();
	m_chaperoneSwitchToBeginnerDistance = settings->value("chaperoneSwitchToBeginnerDistance", 0.5f).toFloat();
	m_enableChaperoneHapticFeedback = settings->value("chaperoneHapticFeedbackEnabled", false).toBool();
	m_chaperoneHapticFeedbackDistance = settings->value("chaperoneHapticFeedbackDistance", 0.5f).toFloat();
	m_enableChaperoneAlarmSound = settings->value("chaperoneAlarmSoundEnabled", false).toBool();
	m_chaperoneAlarmSoundLooping = settings->value("chaperoneAlarmSoundLooping", true).toBool();
	m_chaperoneAlarmSoundAdjustVolume = settings->value("chaperoneAlarmSoundAdjustVolume", false).toBool();
	m_chaperoneAlarmSoundDistance = settings->value("chaperoneAlarmSoundDistance", 0.5f).toFloat();
	m_enableChaperoneShowDashboard = settings->value("chaperoneShowDashboardEnabled", false).toBool();
	m_chaperoneShowDashboardDistance = settings->value("chaperoneShowDashboardDistance", 0.5f).toFloat();
	m_enableChaperoneVelocityModifier = settings->value("chaperoneVelocityModifierEnabled", false).toBool();
	m_chaperoneVelocityModifier = settings->value("chaperoneVelocityModifier", 0.3f).toFloat();
	m_chaperoneVelocityModifierCurrent = 1.0f;
	settings->endGroup();
	
	reloadChaperoneProfiles();
	eventLoopTick(nullptr, 0.0f, 0.0f, 0.0f);
}


void ChaperoneTabController::initStage2(OverlayController * parent, QQuickWindow * widget) {
	this->parent = parent;
	this->widget = widget;
}



ChaperoneTabController::~ChaperoneTabController() {
	m_chaperoneHapticFeedbackActive = false;
	if (m_chaperoneHapticFeedbackThread.joinable()) {
		m_chaperoneHapticFeedbackThread.join();
	}
}


void ChaperoneTabController::reloadChaperoneProfiles() {
	chaperoneProfiles.clear();
	auto settings = OverlayController::appSettings();
	settings->beginGroup("chaperoneSettings");
	auto profileCount = settings->beginReadArray("chaperoneProfiles");
	for (int i = 0; i < profileCount; i++) {
		settings->setArrayIndex(i);
		chaperoneProfiles.emplace_back();
		auto& entry = chaperoneProfiles[i];
		entry.profileName = settings->value("profileName").toString().toStdString();
		entry.includesChaperoneGeometry = settings->value("includesChaperoneGeometry", false).toBool();
		if (entry.includesChaperoneGeometry) {
			entry.chaperoneGeometryQuadCount = settings->value("chaperoneGeometryQuadCount", 0).toUInt();
			entry.chaperoneGeometryQuads.reset(new vr::HmdQuad_t[entry.chaperoneGeometryQuadCount]);
			unsigned qi = 0;
			for (auto q : settings->value("chaperoneGeometryQuads").toList()) {
				unsigned ci = 0;
				for (auto c : q.toList()) {
					unsigned cci = 0;
					for (auto cc : c.toList()) {
						entry.chaperoneGeometryQuads.get()[qi].vCorners[ci].v[cci] = cc.toFloat();
						cci++;
					}
					ci++;
				}
				qi++;
			}
			unsigned i1 = 0;
			for (auto l1 : settings->value("standingCenter").toList()) {
				unsigned i2 = 0;
				for (auto l2 : l1.toList()) {
					entry.standingCenter.m[i1][i2] = l2.toFloat();
					i2++;
				}
				i1++;
			}
			entry.playSpaceAreaX = settings->value("playSpaceAreaX", 0.0f).toFloat();
			entry.playSpaceAreaZ = settings->value("playSpaceAreaZ", 0.0f).toFloat();
		}
		entry.includesVisibility = settings->value("includesVisibility", false).toBool();
		if (entry.includesVisibility) {
			entry.visibility = settings->value("visibility", 0.6).toFloat();
		}
		entry.includesFadeDistance = settings->value("includesFadeDistance", false).toBool();
		if (entry.includesFadeDistance) {
			entry.fadeDistance = settings->value("fadeDistance", 0.7).toFloat();
		}
		entry.includesCenterMarker = settings->value("includesCenterMarker", false).toBool();
		if (entry.includesCenterMarker) {
			entry.centerMarker = settings->value("centerMarker", false).toBool();
		}
		entry.includesPlaySpaceMarker = settings->value("includesPlaySpaceMarker", false).toBool();
		if (entry.includesPlaySpaceMarker) {
			entry.playSpaceMarker = settings->value("playSpaceMarker", false).toBool();
		}
		entry.includesFloorBoundsMarker = settings->value("includesFloorBoundsMarker", false).toBool();
		if (entry.includesFloorBoundsMarker) {
			entry.floorBoundsMarker = settings->value("floorBoundsMarker", false).toBool();
		}
		entry.includesBoundsColor = settings->value("includesBoundsColor", false).toBool();
		if (entry.includesBoundsColor) {
			auto color = settings->value("boundsColor").toList();
			entry.boundsColor[0] = color[0].toInt();
			entry.boundsColor[1] = color[1].toInt();
			entry.boundsColor[2] = color[2].toInt();
		}
		entry.includesChaperoneStyle = settings->value("includesChaperoneStyle", false).toBool();
		if (entry.includesChaperoneStyle) {
			entry.chaperoneStyle = settings->value("chaperoneStyle", 0).toInt();
		}
		entry.includesForceBounds = settings->value("includesForceBounds", false).toBool();
		if (entry.includesForceBounds) {
			entry.forceBounds = settings->value("forceBounds", false).toBool();
		}
		entry.includesProximityWarningSettings = settings->value("includesProximityWarningSettings", false).toBool();
		if (entry.includesProximityWarningSettings){
			entry.enableChaperoneSwitchToBeginner = settings->value("chaperoneSwitchToBeginnerEnabled", false).toBool();
			entry.chaperoneSwitchToBeginnerDistance = settings->value("chaperoneSwitchToBeginnerDistance", 0.5f).toFloat();
			entry.enableChaperoneHapticFeedback = settings->value("chaperoneHapticFeedbackEnabled", false).toBool();
			entry.chaperoneHapticFeedbackDistance = settings->value("chaperoneHapticFeedbackDistance", 0.5f).toFloat();
			entry.enableChaperoneAlarmSound = settings->value("chaperoneAlarmSoundEnabled", false).toBool();
			entry.chaperoneAlarmSoundLooping = settings->value("chaperoneAlarmSoundLooping", true).toBool();
			entry.chaperoneAlarmSoundAdjustVolume = settings->value("chaperoneAlarmSoundAdjustVolume", false).toBool();
			entry.chaperoneAlarmSoundDistance = settings->value("chaperoneAlarmSoundDistance", 0.5f).toFloat();
			entry.enableChaperoneShowDashboard = settings->value("chaperoneShowDashboardEnabled", false).toBool();
			entry.chaperoneShowDashboardDistance = settings->value("chaperoneShowDashboardDistance", 0.5f).toFloat();
			entry.enableChaperoneVelocityModifier = settings->value("chaperoneVelocityModifierEnabled", false).toBool();
			entry.chaperoneVelocityModifier = settings->value("chaperoneVelocityModifier", 0.3f).toFloat();
		}
	}
	settings->endArray();
	settings->endGroup();
}

void ChaperoneTabController::saveChaperoneProfiles() {
	auto settings = OverlayController::appSettings();
	settings->beginGroup("chaperoneSettings");
	settings->beginWriteArray("chaperoneProfiles");
	unsigned i = 0;
	for (auto& p : chaperoneProfiles) {
		settings->setArrayIndex(i);
		settings->setValue("profileName", QString::fromStdString(p.profileName));
		settings->setValue("includesChaperoneGeometry", p.includesChaperoneGeometry);
		if (p.includesChaperoneGeometry) {
			settings->setValue("chaperoneGeometryQuadCount", p.chaperoneGeometryQuadCount);
			QVariantList quadList;
			for (unsigned qi = 0; qi < p.chaperoneGeometryQuadCount; qi++) {
				QVariantList cornerList;
				for (unsigned ci = 0; ci < 4; ci++) {
					QVariantList coordVector;
					for (unsigned cci = 0; cci < 3; cci++) {
						coordVector.push_back(p.chaperoneGeometryQuads.get()[qi].vCorners[ci].v[cci]);
					}
					cornerList.push_back(coordVector);
				}
				quadList.push_back(cornerList);
			}
			settings->setValue("chaperoneGeometryQuads", quadList);
			QVariantList l1;
			for (unsigned i1 = 0; i1 < 3; i1++) {
				QVariantList l2;
				for (unsigned i2 = 0; i2 < 4; i2++) {
					l2.push_back(p.standingCenter.m[i1][i2]);
				}
				l1.push_back(l2);
			}
			settings->setValue("standingCenter", l1);
			settings->setValue("playSpaceAreaX", p.playSpaceAreaX);
			settings->setValue("playSpaceAreaZ", p.playSpaceAreaZ);
		}
		settings->setValue("includesVisibility", p.includesVisibility);
		if (p.includesVisibility) {
			settings->setValue("visibility", p.visibility);
		}
		settings->setValue("includesFadeDistance", p.includesFadeDistance);
		if (p.includesFadeDistance) {
			settings->setValue("fadeDistance", p.fadeDistance);
		}
		settings->setValue("includesCenterMarker", p.includesCenterMarker);
		if (p.includesCenterMarker) {
			settings->setValue("centerMarker", p.centerMarker);
		}
		settings->setValue("includesPlaySpaceMarker", p.includesPlaySpaceMarker);
		if (p.includesPlaySpaceMarker) {
			settings->setValue("playSpaceMarker", p.playSpaceMarker);
		}
		settings->setValue("includesFloorBoundsMarker", p.includesFloorBoundsMarker);
		if (p.includesFloorBoundsMarker) {
			settings->setValue("floorBoundsMarker", p.floorBoundsMarker);
		}
		settings->setValue("includesBoundsColor", p.includesBoundsColor);
		if (p.includesBoundsColor) {
			QVariantList color;
			color.push_back(p.boundsColor[0]);
			color.push_back(p.boundsColor[1]);
			color.push_back(p.boundsColor[2]);
			settings->setValue("boundsColor", color);
		}
		settings->setValue("includesChaperoneStyle", p.includesChaperoneStyle);
		if (p.includesChaperoneStyle) {
			settings->setValue("chaperoneStyle", p.chaperoneStyle);
		}
		settings->setValue("includesForceBounds", p.includesForceBounds);
		if (p.includesForceBounds) {
			settings->setValue("forceBounds", p.forceBounds);
		}
		settings->setValue("includesProximityWarningSettings", p.includesProximityWarningSettings);
		if (p.includesProximityWarningSettings) {
			settings->setValue("chaperoneSwitchToBeginnerEnabled", p.enableChaperoneSwitchToBeginner);
			settings->setValue("chaperoneSwitchToBeginnerDistance", p.chaperoneSwitchToBeginnerDistance);
			settings->setValue("chaperoneHapticFeedbackEnabled", p.enableChaperoneHapticFeedback);
			settings->setValue("chaperoneHapticFeedbackDistance", p.chaperoneHapticFeedbackDistance);
			settings->setValue("chaperoneAlarmSoundEnabled", p.enableChaperoneAlarmSound);
			settings->setValue("chaperoneAlarmSoundLooping", p.chaperoneAlarmSoundLooping);
			settings->setValue("chaperoneAlarmSoundAdjustVolume", p.chaperoneAlarmSoundAdjustVolume);
			settings->setValue("chaperoneAlarmSoundDistance", p.chaperoneAlarmSoundDistance);
			settings->setValue("chaperoneShowDashboardEnabled", p.enableChaperoneShowDashboard);
			settings->setValue("chaperoneShowDashboardDistance", p.chaperoneShowDashboardDistance);
			settings->setValue("chaperoneVelocityModifierEnabled", p.enableChaperoneVelocityModifier);
			settings->setValue("chaperoneVelocityModifier", p.chaperoneVelocityModifier);
		}
		i++;
	}
	settings->endArray();
	settings->endGroup();
}


void ChaperoneTabController::handleChaperoneWarnings(float distance) {
	vr::VRControllerState_t hmdState;
	vr::VRSystem()->GetControllerState(vr::k_unTrackedDeviceIndex_Hmd, &hmdState, sizeof(vr::VRControllerState_t));

	// Switch to Beginner Mode
	if (m_enableChaperoneSwitchToBeginner) {
		float activationDistance = m_chaperoneSwitchToBeginnerDistance * m_chaperoneVelocityModifierCurrent;
		if (distance <= activationDistance && (hmdState.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_ProximitySensor)) && !m_chaperoneSwitchToBeginnerActive) {
			vr::EVRSettingsError vrSettingsError;
			m_chaperoneSwitchToBeginnerLastStyle = vr::VRSettings()->GetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_Style_Int32, &vrSettingsError);
			if (vrSettingsError != vr::VRSettingsError_None) {
				LOG(WARNING) << "Could not read \"" << vr::k_pch_CollisionBounds_Style_Int32 << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
			} else {
				vr::VRSettings()->SetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_Style_Int32, vr::COLLISION_BOUNDS_STYLE_BEGINNER, &vrSettingsError);
				if (vrSettingsError != vr::VRSettingsError_None) {
					LOG(WARNING) << "Could not set \"" << vr::k_pch_CollisionBounds_Style_Int32 << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
				} else {
					vr::VRSettings()->Sync(true);
					m_chaperoneSwitchToBeginnerActive = true;
				}
			}
		} else if ((distance > activationDistance || !(hmdState.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_ProximitySensor))) && m_chaperoneSwitchToBeginnerActive) {
			vr::EVRSettingsError vrSettingsError;
			vr::VRSettings()->SetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_Style_Int32, m_chaperoneSwitchToBeginnerLastStyle, &vrSettingsError);
			if (vrSettingsError != vr::VRSettingsError_None) {
				LOG(WARNING) << "Could not set \"" << vr::k_pch_CollisionBounds_Style_Int32 << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
			} else {
				vr::VRSettings()->Sync(true);
				m_chaperoneSwitchToBeginnerActive = false;
			}
		}
	}

	// Haptic Feedback
	if (m_enableChaperoneHapticFeedback) {
		float activationDistance = m_chaperoneHapticFeedbackDistance * m_chaperoneVelocityModifierCurrent;
		if (distance <= activationDistance && (hmdState.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_ProximitySensor))) {
			if (!m_chaperoneHapticFeedbackActive) {
				if (m_chaperoneHapticFeedbackThread.joinable()) {
					m_chaperoneHapticFeedbackActive = false;
					m_chaperoneHapticFeedbackThread.join();
				}
				m_chaperoneHapticFeedbackActive = true;
				m_chaperoneHapticFeedbackThread = std::thread([](ChaperoneTabController* _this) {
					auto leftIndex = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand);
					auto rightIndex = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand);
					while (_this->m_chaperoneHapticFeedbackActive) {
						if (leftIndex != vr::k_unTrackedDeviceIndexInvalid) {
							vr::VRSystem()->TriggerHapticPulse(leftIndex, 0, 2000);
						}
						if (rightIndex != vr::k_unTrackedDeviceIndexInvalid) {
							vr::VRSystem()->TriggerHapticPulse(rightIndex, 0, 2000);
						}
						std::this_thread::sleep_for(std::chrono::milliseconds(5));
					}
				}, this);
			}
		} else if ((distance > activationDistance || !(hmdState.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_ProximitySensor))) && m_chaperoneHapticFeedbackActive) {
			m_chaperoneHapticFeedbackActive = false;
		}
	}

	// Alarm Sound
	if (m_enableChaperoneAlarmSound) {
		float activationDistance = m_chaperoneAlarmSoundDistance * m_chaperoneVelocityModifierCurrent;
		if (distance <= activationDistance && (hmdState.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_ProximitySensor))) {
			if (!m_chaperoneAlarmSoundActive) {
				parent->playAlarm01Sound(m_chaperoneAlarmSoundLooping);
				m_chaperoneAlarmSoundActive = true;
			}
			if (m_chaperoneAlarmSoundLooping && m_chaperoneAlarmSoundAdjustVolume) {
				float vol = 1.1f - distance / activationDistance;
				if (vol > 1.0f) {
					vol = 1.0f;
				}
				parent->setAlarm01SoundVolume(vol);
			} else {
				parent->setAlarm01SoundVolume(1.0f);
			}
		} else if ((distance > activationDistance || !(hmdState.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_ProximitySensor))) && m_chaperoneAlarmSoundActive) {
			parent->cancelAlarm01Sound();
			m_chaperoneAlarmSoundActive = false;
		}
	}

	// Show Dashboard
	if (m_enableChaperoneShowDashboard) {
		float activationDistance = m_chaperoneShowDashboardDistance * m_chaperoneVelocityModifierCurrent;
		if (distance <= activationDistance && !m_chaperoneShowDashboardActive) {
			if (!vr::VROverlay()->IsDashboardVisible()) {
				vr::VROverlay()->ShowDashboard(OverlayController::applicationKey);
			}
			m_chaperoneShowDashboardActive = true;
		} else if (distance > activationDistance && m_chaperoneShowDashboardActive) {
			m_chaperoneShowDashboardActive = false;
		}
	}
}


void ChaperoneTabController::eventLoopTick(vr::TrackedDevicePose_t* devicePoses, float leftSpeed, float rightSpeed, float hmdSpeed) {
	m_chaperoneVelocityModifierCurrent = 1.0f;
	if (m_enableChaperoneVelocityModifier) {
		float mod = m_chaperoneVelocityModifier * std::max({ leftSpeed, rightSpeed, hmdSpeed });
		if (mod > 0.02) {
			m_chaperoneVelocityModifierCurrent += mod;
		}
	}
	//LOG(INFO) << "m_chaperoneVelocityModifierCurrent: " << m_chaperoneVelocityModifierCurrent;
	float newFadeDistance = m_fadeDistance * m_chaperoneVelocityModifierCurrent;
	if (m_fadeDistanceModified != newFadeDistance) {
		m_fadeDistanceModified = newFadeDistance;
		vr::VRSettings()->SetFloat(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_FadeDistance_Float, m_fadeDistanceModified);
		vr::VRSettings()->Sync();
	}

	if (devicePoses) {
		std::lock_guard<std::recursive_mutex> lock(parent->chaperoneUtils().mutex());
		auto minDistance = NAN;
		auto& poseHmd = devicePoses[vr::k_unTrackedDeviceIndex_Hmd];
		if (poseHmd.bPoseIsValid && poseHmd.bDeviceIsConnected && poseHmd.eTrackingResult == vr::TrackingResult_Running_OK) {
			auto distanceHmd = parent->chaperoneUtils().getDistanceToChaperone({
				poseHmd.mDeviceToAbsoluteTracking.m[0][3],
				poseHmd.mDeviceToAbsoluteTracking.m[1][3],
				poseHmd.mDeviceToAbsoluteTracking.m[2][3]
			});
			if (!isnan(distanceHmd)) {
				minDistance = distanceHmd;
			}
		}
		auto leftIndex = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand);
		if (leftIndex != vr::k_unTrackedDeviceIndexInvalid) {
			auto& poseLeft = devicePoses[leftIndex];
			if (poseLeft.bPoseIsValid && poseLeft.bDeviceIsConnected && poseLeft.eTrackingResult == vr::TrackingResult_Running_OK) {
				auto distanceLeft = parent->chaperoneUtils().getDistanceToChaperone({
					poseLeft.mDeviceToAbsoluteTracking.m[0][3],
					poseLeft.mDeviceToAbsoluteTracking.m[1][3],
					poseLeft.mDeviceToAbsoluteTracking.m[2][3]
				});
				if (!isnan(distanceLeft) && (isnan(minDistance) || distanceLeft < minDistance)) {
					minDistance = distanceLeft;
				}
			}
		}
		auto rightIndex = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand);
		if (rightIndex != vr::k_unTrackedDeviceIndexInvalid) {
			auto& poseRight = devicePoses[rightIndex];
			if (poseRight.bPoseIsValid && poseRight.bDeviceIsConnected && poseRight.eTrackingResult == vr::TrackingResult_Running_OK) {
				auto distanceRight = parent->chaperoneUtils().getDistanceToChaperone({
					poseRight.mDeviceToAbsoluteTracking.m[0][3],
					poseRight.mDeviceToAbsoluteTracking.m[1][3],
					poseRight.mDeviceToAbsoluteTracking.m[2][3]
				});
				if (!isnan(distanceRight) && (isnan(minDistance) || distanceRight < minDistance)) {
					minDistance = distanceRight;
				}
			}
		}
		if (!isnan(minDistance)) {
			handleChaperoneWarnings(minDistance);
		}
	}

	if (settingsUpdateCounter >= 50) {
		vr::EVRSettingsError vrSettingsError;
		float vis = vr::VRSettings()->GetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_ColorGammaA_Int32, &vrSettingsError);
		if (vrSettingsError != vr::VRSettingsError_None) {
			LOG(WARNING) << "Could not read \"" << vr::k_pch_CollisionBounds_ColorGammaA_Int32 << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
		}
		setBoundsVisibility(vis/255.0f);
		if (m_chaperoneVelocityModifierCurrent == 1.0f) {
			auto fd = vr::VRSettings()->GetFloat(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_FadeDistance_Float, &vrSettingsError);
			if (vrSettingsError != vr::VRSettingsError_None) {
				LOG(WARNING) << "Could not read \"" << vr::k_pch_CollisionBounds_FadeDistance_Float << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
			}
			setFadeDistance(fd);
		}
		auto cm = vr::VRSettings()->GetBool(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_CenterMarkerOn_Bool, &vrSettingsError);
		if (vrSettingsError != vr::VRSettingsError_None) {
			LOG(WARNING) << "Could not read \"" << vr::k_pch_CollisionBounds_CenterMarkerOn_Bool << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
		}
		setCenterMarker(cm);
		auto ps = vr::VRSettings()->GetBool(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_PlaySpaceOn_Bool, &vrSettingsError);
		if (vrSettingsError != vr::VRSettingsError_None) {
			LOG(WARNING) << "Could not read \"" << vr::k_pch_CollisionBounds_PlaySpaceOn_Bool << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
		}
		setPlaySpaceMarker(ps);
		updateHeight(getBoundsMaxY());
		settingsUpdateCounter = 0;
	} else {
		settingsUpdateCounter++;
	}
}


float ChaperoneTabController::boundsVisibility() const {
	return m_visibility;
}


void ChaperoneTabController::setBoundsVisibility(float value, bool notify) {
	if (m_visibility != value) {
		m_visibility = value;
		vr::VRSettings()->SetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_ColorGammaA_Int32, 255 * m_visibility);
		vr::VRSettings()->Sync();
		if (notify) {
			emit boundsVisibilityChanged(m_visibility);
		}
	}
}


float ChaperoneTabController::fadeDistance() const {
	return m_fadeDistance;
}


void ChaperoneTabController::setFadeDistance(float value, bool notify) {
	if (m_fadeDistance != value) {
		m_fadeDistance = value;
		vr::VRSettings()->SetFloat(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_FadeDistance_Float, m_fadeDistance);
		vr::VRSettings()->Sync();
		if (notify) {
			emit fadeDistanceChanged(m_fadeDistance);
		}
	}
}


float ChaperoneTabController::height() const {
	return m_height;
}


void ChaperoneTabController::setHeight(float value, bool notify) {
	if (m_height != value) {
		m_height = value;
		vr::VRChaperoneSetup()->RevertWorkingCopy();
		unsigned collisionBoundsCount = 0;
		vr::VRChaperoneSetup()->GetWorkingCollisionBoundsInfo(nullptr, &collisionBoundsCount);
		if (collisionBoundsCount > 0) {
			vr::HmdQuad_t* collisionBounds = new vr::HmdQuad_t[collisionBoundsCount];
			vr::VRChaperoneSetup()->GetWorkingCollisionBoundsInfo(collisionBounds, &collisionBoundsCount);
			for (unsigned b = 0; b < collisionBoundsCount; b++) {
				collisionBounds[b].vCorners[0].v[1] = 0.0;
				collisionBounds[b].vCorners[1].v[1] = value;
				collisionBounds[b].vCorners[2].v[1] = value;
				collisionBounds[b].vCorners[3].v[1] = 0.0;
			}
			vr::VRChaperoneSetup()->SetWorkingCollisionBoundsInfo(collisionBounds, collisionBoundsCount);
			vr::VRChaperoneSetup()->CommitWorkingCopy(vr::EChaperoneConfigFile_Live);
			delete collisionBounds;
		}
		if (notify) {
			emit heightChanged(m_fadeDistance);
		}
	}
}


void ChaperoneTabController::updateHeight(float value, bool notify) {
	if (m_height != value) {
		m_height = value;
		if (notify) {
			emit heightChanged(m_fadeDistance);
		}
	}
}


bool ChaperoneTabController::centerMarker() const {
	return m_centerMarker;
}

void ChaperoneTabController::setCenterMarker(bool value, bool notify) {
	if (m_centerMarker != value) {
		m_centerMarker = value;
		vr::VRSettings()->SetBool(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_CenterMarkerOn_Bool, m_centerMarker);
		vr::VRSettings()->Sync();
		if (notify) {
			emit centerMarkerChanged(m_centerMarker);
		}
	}
}

bool ChaperoneTabController::playSpaceMarker() const {
	return m_playSpaceMarker;
}

void ChaperoneTabController::setPlaySpaceMarker(bool value, bool notify) {
	if (m_playSpaceMarker != value) {
		m_playSpaceMarker = value;
		vr::VRSettings()->SetBool(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_PlaySpaceOn_Bool, m_playSpaceMarker);
		vr::VRSettings()->Sync();
		if (notify) {
			emit playSpaceMarkerChanged(m_playSpaceMarker);
		}
	}
}

bool ChaperoneTabController::forceBounds() const {
	return m_forceBounds;
}


bool ChaperoneTabController::isChaperoneSwitchToBeginnerEnabled() const {
	return m_enableChaperoneSwitchToBeginner;
}

float ChaperoneTabController::chaperoneSwitchToBeginnerDistance() const {
	return m_chaperoneSwitchToBeginnerDistance;
}


bool ChaperoneTabController::isChaperoneHapticFeedbackEnabled() const {
	return m_enableChaperoneHapticFeedback;
}

float ChaperoneTabController::chaperoneHapticFeedbackDistance() const {
	return m_chaperoneHapticFeedbackDistance;
}


bool ChaperoneTabController::isChaperoneAlarmSoundEnabled() const {
	return m_enableChaperoneAlarmSound;
}

bool ChaperoneTabController::isChaperoneAlarmSoundLooping() const {
	return m_chaperoneAlarmSoundLooping;
}

bool ChaperoneTabController::isChaperoneAlarmSoundAdjustVolume() const {
	return m_chaperoneAlarmSoundAdjustVolume;
}

float ChaperoneTabController::chaperoneAlarmSoundDistance() const {
	return m_chaperoneAlarmSoundDistance;
}


bool ChaperoneTabController::isChaperoneShowDashboardEnabled() const {
	return m_enableChaperoneShowDashboard;
}

float ChaperoneTabController::chaperoneShowDashboardDistance() const {
	return m_chaperoneShowDashboardDistance;
}


bool ChaperoneTabController::isChaperoneVelocityModifierEnabled() const {
	return m_enableChaperoneVelocityModifier;
}

float ChaperoneTabController::chaperoneVelocityModifier() const {
	return m_chaperoneVelocityModifier;
}




Q_INVOKABLE unsigned ChaperoneTabController::getChaperoneProfileCount() {
	return (unsigned)chaperoneProfiles.size();
}

Q_INVOKABLE QString ChaperoneTabController::getChaperoneProfileName(unsigned index) {
	if (index >= chaperoneProfiles.size()) {
		return QString();
	} else {
		return QString::fromStdString(chaperoneProfiles[index].profileName);
	}
}

void ChaperoneTabController::setForceBounds(bool value, bool notify) {
	if (m_forceBounds != value) {
		m_forceBounds = value;
		vr::VRChaperone()->ForceBoundsVisible(m_forceBounds);
		if (notify) {
			emit forceBoundsChanged(m_forceBounds);
		}
	}
}


void ChaperoneTabController::setChaperoneSwitchToBeginnerEnabled(bool value, bool notify) {
	if (m_enableChaperoneSwitchToBeginner != value) {
		if (!value && m_chaperoneSwitchToBeginnerActive) {
			vr::EVRSettingsError vrSettingsError;
			vr::VRSettings()->SetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_Style_Int32, m_chaperoneSwitchToBeginnerLastStyle, &vrSettingsError);
			if (vrSettingsError != vr::VRSettingsError_None) {
				LOG(WARNING) << "Could not set \"" << vr::k_pch_CollisionBounds_Style_Int32 << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
			} else {
				vr::VRSettings()->Sync(true);
			}
		}
		m_enableChaperoneSwitchToBeginner = value;
		m_chaperoneSwitchToBeginnerActive = false;
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneSwitchToBeginnerEnabled", m_enableChaperoneSwitchToBeginner);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneSwitchToBeginnerEnabledChanged(m_enableChaperoneSwitchToBeginner);
		}
	}
}

void ChaperoneTabController::setChaperoneSwitchToBeginnerDistance(float value, bool notify) {
	if (m_chaperoneSwitchToBeginnerDistance != value) {
		m_chaperoneSwitchToBeginnerDistance = value;
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneSwitchToBeginnerDistance", m_chaperoneSwitchToBeginnerDistance);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneSwitchToBeginnerDistanceChanged(m_chaperoneSwitchToBeginnerDistance);
		}
	}
}


void ChaperoneTabController::setChaperoneHapticFeedbackEnabled(bool value, bool notify) {
	if (m_enableChaperoneHapticFeedback != value) {
		m_chaperoneHapticFeedbackActive = false;
		if (m_chaperoneHapticFeedbackThread.joinable()) {
			m_chaperoneHapticFeedbackThread.join();
		}
		m_enableChaperoneHapticFeedback = value;
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneHapticFeedbackEnabled", m_enableChaperoneHapticFeedback);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneHapticFeedbackEnabledChanged(m_enableChaperoneHapticFeedback);
		}
	}
}

void ChaperoneTabController::setChaperoneHapticFeedbackDistance(float value, bool notify) {
	if (m_chaperoneHapticFeedbackDistance != value) {
		m_chaperoneHapticFeedbackDistance = value;
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneHapticFeedbackDistance", m_chaperoneHapticFeedbackDistance);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneHapticFeedbackDistanceChanged(m_chaperoneHapticFeedbackDistance);
		}
	}
}


void ChaperoneTabController::setChaperoneAlarmSoundEnabled(bool value, bool notify) {
	if (m_enableChaperoneAlarmSound != value) {
		if (!value && m_chaperoneAlarmSoundActive) {
			parent->cancelAlarm01Sound();
		}
		m_enableChaperoneAlarmSound = value;
		m_chaperoneAlarmSoundActive = false;
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneAlarmSoundEnabled", m_enableChaperoneAlarmSound);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneAlarmSoundEnabledChanged(m_enableChaperoneAlarmSound);
		}
	}
}

void ChaperoneTabController::setChaperoneAlarmSoundLooping(bool value, bool notify) {
	if (m_chaperoneAlarmSoundLooping != value) {
		m_chaperoneAlarmSoundLooping = value;
		if (m_enableChaperoneAlarmSound && m_chaperoneAlarmSoundActive) {
			if (m_chaperoneAlarmSoundLooping) {
				parent->playAlarm01Sound(m_chaperoneAlarmSoundLooping);
			} else {
				parent->cancelAlarm01Sound();
			}
		}
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneAlarmSoundLooping", m_chaperoneAlarmSoundLooping);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneAlarmSoundLoopingChanged(m_chaperoneAlarmSoundLooping);
		}
	}
}

void ChaperoneTabController::setChaperoneAlarmSoundAdjustVolume(bool value, bool notify) {
	if (m_chaperoneAlarmSoundAdjustVolume != value) {
		m_chaperoneAlarmSoundAdjustVolume = value;
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneAlarmSoundAdjustVolume", m_chaperoneAlarmSoundAdjustVolume);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneAlarmSoundAdjustVolumeChanged(m_chaperoneAlarmSoundAdjustVolume);
		}
	}
}

void ChaperoneTabController::setChaperoneAlarmSoundDistance(float value, bool notify) {
	if (m_chaperoneAlarmSoundDistance != value) {
		m_chaperoneAlarmSoundDistance = value;
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneAlarmSoundDistance", m_chaperoneAlarmSoundDistance);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneAlarmSoundDistanceChanged(m_chaperoneAlarmSoundDistance);
		}
	}
}


void ChaperoneTabController::setChaperoneShowDashboardEnabled(bool value, bool notify) {
	if (m_enableChaperoneShowDashboard != value) {
		m_enableChaperoneShowDashboard = value;
		m_chaperoneShowDashboardActive = false;
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneShowDashboardEnabled", m_enableChaperoneShowDashboard);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneShowDashboardEnabledChanged(m_enableChaperoneShowDashboard);
		}
	}
}

void ChaperoneTabController::setChaperoneShowDashboardDistance(float value, bool notify) {
	if (m_chaperoneShowDashboardDistance != value) {
		m_chaperoneShowDashboardDistance = value;
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneShowDashboardDistance", m_chaperoneShowDashboardDistance);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneShowDashboardDistanceChanged(m_chaperoneShowDashboardDistance);
		}
	}
}


void ChaperoneTabController::setChaperoneVelocityModifierEnabled(bool value, bool notify) {
	if (m_enableChaperoneVelocityModifier != value) {
		m_enableChaperoneVelocityModifier = value;
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneVelocityModifierEnabled", m_enableChaperoneVelocityModifier);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneVelocityModifierEnabledChanged(m_enableChaperoneVelocityModifier);
		}
	}
}

void ChaperoneTabController::setChaperoneVelocityModifier(float value, bool notify) {
	if (m_chaperoneVelocityModifier != value) {
		m_chaperoneVelocityModifier = value;
		auto settings = OverlayController::appSettings();
		settings->beginGroup("chaperoneSettings");
		settings->setValue("chaperoneVelocityModifier", m_chaperoneVelocityModifier);
		settings->endGroup();
		settings->sync();
		if (notify) {
			emit chaperoneVelocityModifierChanged(m_chaperoneVelocityModifier);
		}
	}
}



void ChaperoneTabController::flipOrientation() {
	parent->RotateUniverseCenter(vr::TrackingUniverseStanding, (float)M_PI);
}

void ChaperoneTabController::reloadFromDisk() {
	vr::VRChaperoneSetup()->ReloadFromDisk(vr::EChaperoneConfigFile_Live);
	vr::VRChaperoneSetup()->CommitWorkingCopy(vr::EChaperoneConfigFile_Live);
}

void ChaperoneTabController::addChaperoneProfile(QString name, bool includeGeometry, bool includeVisbility, bool includeFadeDistance, bool includeCenterMarker,
		bool includePlaySpaceMarker, bool includeFloorBounds, bool includeBoundsColor, bool includeChaperoneStyle, bool includeForceBounds, bool includesProximityWarningSettings) {
	vr::EVRSettingsError vrSettingsError;
	ChaperoneProfile* profile = nullptr;
	for (auto& p : chaperoneProfiles) {
		if (p.profileName.compare(name.toStdString()) == 0) {
			profile = &p;
			break;
		}
	}
	if (!profile) {
		auto i = chaperoneProfiles.size();
		chaperoneProfiles.emplace_back();
		profile = &chaperoneProfiles[i];
	}
	profile->profileName = name.toStdString();
	profile->includesChaperoneGeometry = includeGeometry;
	if (includeGeometry) {
		vr::VRChaperoneSetup()->RevertWorkingCopy();
		uint32_t quadCount = 0;
		vr::VRChaperoneSetup()->GetLiveCollisionBoundsInfo(nullptr, &quadCount);
		profile->chaperoneGeometryQuadCount = quadCount;
		profile->chaperoneGeometryQuads.reset(new vr::HmdQuad_t[quadCount]);
		vr::VRChaperoneSetup()->GetLiveCollisionBoundsInfo(profile->chaperoneGeometryQuads.get(), &quadCount);
		vr::VRChaperoneSetup()->GetWorkingStandingZeroPoseToRawTrackingPose(&profile->standingCenter);
		vr::VRChaperoneSetup()->GetWorkingPlayAreaSize(&profile->playSpaceAreaX, &profile->playSpaceAreaZ);
	}
	profile->includesVisibility = includeVisbility;
	if (includeVisbility) {
		profile->visibility = m_visibility;
	}
	profile->includesFadeDistance = includeFadeDistance;
	if (includeFadeDistance) {
		profile->fadeDistance = m_fadeDistance;
	}
	profile->includesCenterMarker = includeCenterMarker;
	if (includeCenterMarker) {
		profile->centerMarker = m_centerMarker;
	}
	profile->includesPlaySpaceMarker = includePlaySpaceMarker;
	if (includePlaySpaceMarker) {
		profile->playSpaceMarker = m_playSpaceMarker;
	}
	profile->includesFloorBoundsMarker = includeFloorBounds;
	if (includeFloorBounds) {
		profile->floorBoundsMarker = vr::VRSettings()->GetBool(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_GroundPerimeterOn_Bool, &vrSettingsError);
		if (vrSettingsError != vr::VRSettingsError_None) {
			LOG(WARNING) << "Could not read \"" << vr::k_pch_CollisionBounds_GroundPerimeterOn_Bool << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
		}
	}
	profile->includesBoundsColor = includeBoundsColor;
	if (includeBoundsColor) {
		profile->boundsColor[0] = vr::VRSettings()->GetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_ColorGammaR_Int32, &vrSettingsError);
		if (vrSettingsError != vr::VRSettingsError_None) {
			LOG(WARNING) << "Could not read \"" << vr::k_pch_CollisionBounds_ColorGammaR_Int32 << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
		}
		profile->boundsColor[1] = vr::VRSettings()->GetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_ColorGammaG_Int32, &vrSettingsError);
		if (vrSettingsError != vr::VRSettingsError_None) {
			LOG(WARNING) << "Could not read \"" << vr::k_pch_CollisionBounds_ColorGammaG_Int32 << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
		}
		profile->boundsColor[2] = vr::VRSettings()->GetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_ColorGammaB_Int32, &vrSettingsError);
		if (vrSettingsError != vr::VRSettingsError_None) {
			LOG(WARNING) << "Could not read \"" << vr::k_pch_CollisionBounds_ColorGammaB_Int32 << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
		}
	}
	profile->includesChaperoneStyle = includeChaperoneStyle;
	if (includeChaperoneStyle) {
		profile->chaperoneStyle = vr::VRSettings()->GetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_Style_Int32, &vrSettingsError);
		if (vrSettingsError != vr::VRSettingsError_None) {
			LOG(WARNING) << "Could not read \"" << vr::k_pch_CollisionBounds_Style_Int32 << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
		}
	}
	profile->includesForceBounds = includeForceBounds;
	if (includeForceBounds) {
		profile->forceBounds = m_forceBounds;
	}
	profile->includesProximityWarningSettings = includesProximityWarningSettings;
	if (includesProximityWarningSettings) {
		profile->enableChaperoneSwitchToBeginner = m_enableChaperoneSwitchToBeginner;
		profile->chaperoneSwitchToBeginnerDistance = m_chaperoneSwitchToBeginnerDistance;
		profile->enableChaperoneHapticFeedback = m_enableChaperoneHapticFeedback;
		profile->chaperoneHapticFeedbackDistance = m_chaperoneHapticFeedbackDistance;
		profile->enableChaperoneAlarmSound = m_enableChaperoneAlarmSound;
		profile->chaperoneAlarmSoundLooping = m_chaperoneAlarmSoundLooping;
		profile->chaperoneAlarmSoundAdjustVolume = m_chaperoneAlarmSoundAdjustVolume;
		profile->chaperoneAlarmSoundDistance = m_chaperoneAlarmSoundDistance;
		profile->enableChaperoneShowDashboard = m_enableChaperoneShowDashboard;
		profile->chaperoneShowDashboardDistance = m_chaperoneShowDashboardDistance;
		profile->enableChaperoneVelocityModifier = m_enableChaperoneVelocityModifier;
		profile->chaperoneVelocityModifier = m_chaperoneVelocityModifier;
	}
	saveChaperoneProfiles();
	OverlayController::appSettings()->sync();
	emit chaperoneProfilesUpdated();
}

void ChaperoneTabController::applyChaperoneProfile(unsigned index) {
	if (index < chaperoneProfiles.size()) {
		auto& profile = chaperoneProfiles[index];
		if (profile.includesChaperoneGeometry) {
			vr::VRChaperoneSetup()->RevertWorkingCopy();
			vr::VRChaperoneSetup()->SetWorkingCollisionBoundsInfo(profile.chaperoneGeometryQuads.get(), profile.chaperoneGeometryQuadCount);
			vr::VRChaperoneSetup()->SetWorkingStandingZeroPoseToRawTrackingPose(&profile.standingCenter);
			vr::VRChaperoneSetup()->SetWorkingPlayAreaSize(profile.playSpaceAreaX, profile.playSpaceAreaZ);
			vr::VRChaperoneSetup()->CommitWorkingCopy(vr::EChaperoneConfigFile_Live);
		}
		if (profile.includesVisibility) {
			setBoundsVisibility(profile.visibility);
		}
		if (profile.includesFadeDistance) {
			setFadeDistance(profile.fadeDistance);
		}
		if (profile.includesCenterMarker) {
			setCenterMarker(profile.centerMarker);
		}
		if (profile.includesPlaySpaceMarker) {
			setPlaySpaceMarker(profile.playSpaceMarker);
		}
		if (profile.includesFloorBoundsMarker) {
			vr::VRSettings()->SetBool(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_GroundPerimeterOn_Bool, profile.floorBoundsMarker);
		}
		if (profile.includesBoundsColor) {
			vr::VRSettings()->SetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_ColorGammaR_Int32, profile.boundsColor[0]);
			vr::VRSettings()->SetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_ColorGammaG_Int32, profile.boundsColor[1]);
			vr::VRSettings()->SetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_ColorGammaB_Int32, profile.boundsColor[2]);
		}
		if (profile.includesChaperoneStyle) {
			vr::VRSettings()->SetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_Style_Int32, profile.chaperoneStyle);
		}
		if (profile.includesForceBounds) {
			setForceBounds(profile.forceBounds);
		}
		if (profile.includesProximityWarningSettings) {
			setChaperoneSwitchToBeginnerDistance(profile.chaperoneSwitchToBeginnerDistance);
			setChaperoneSwitchToBeginnerEnabled(profile.enableChaperoneSwitchToBeginner);
			setChaperoneHapticFeedbackDistance(profile.chaperoneHapticFeedbackDistance);
			setChaperoneHapticFeedbackEnabled(profile.enableChaperoneHapticFeedback);
			setChaperoneAlarmSoundLooping(profile.chaperoneAlarmSoundLooping);
			setChaperoneAlarmSoundAdjustVolume(profile.chaperoneAlarmSoundAdjustVolume);
			setChaperoneAlarmSoundDistance(profile.chaperoneAlarmSoundDistance);
			setChaperoneAlarmSoundEnabled(profile.enableChaperoneAlarmSound);
			setChaperoneShowDashboardDistance(profile.chaperoneShowDashboardDistance);
			setChaperoneShowDashboardEnabled(profile.enableChaperoneShowDashboard);
			setChaperoneVelocityModifier(profile.chaperoneVelocityModifier);
			setChaperoneVelocityModifierEnabled(profile.enableChaperoneVelocityModifier);
		}
		vr::VRSettings()->Sync(true);
		updateHeight(getBoundsMaxY());
	}
}

void ChaperoneTabController::deleteChaperoneProfile(unsigned index) {
	if (index < chaperoneProfiles.size()) {
		auto pos = chaperoneProfiles.begin() + index;
		chaperoneProfiles.erase(pos);
		saveChaperoneProfiles();
		OverlayController::appSettings()->sync();
		emit chaperoneProfilesUpdated();
	}
}

float ChaperoneTabController::getBoundsMaxY() {
	unsigned collisionBoundsCount = 0;
	float result = FP_NAN;
	vr::VRChaperoneSetup()->GetLiveCollisionBoundsInfo(nullptr, &collisionBoundsCount);
	if (collisionBoundsCount > 0) {
		vr::HmdQuad_t* collisionBounds = new vr::HmdQuad_t[collisionBoundsCount];
		vr::VRChaperoneSetup()->GetLiveCollisionBoundsInfo(collisionBounds, &collisionBoundsCount);
		for (unsigned b = 0; b < collisionBoundsCount; b++) {
			int ci;
			if (collisionBounds[b].vCorners[1].v[1] >= collisionBounds[b].vCorners[2].v[1]) {
				ci = 1;
			} else {
				ci = 2;
			}
			if (isnan(result) || result < collisionBounds[b].vCorners[ci].v[1]) {
				result = collisionBounds[b].vCorners[ci].v[1];
			}
		}
		delete collisionBounds;
	}
	return result;
}

void ChaperoneTabController::reset() {
	vr::EVRSettingsError vrSettingsError;

	vr::VRSettings()->RemoveKeyInSection(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_ColorGammaA_Int32, &vrSettingsError);
	if (vrSettingsError != vr::VRSettingsError_None) {
		LOG(WARNING) << "Could not remove \"" << vr::k_pch_CollisionBounds_ColorGammaA_Int32 << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
	}

	vr::VRSettings()->RemoveKeyInSection(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_FadeDistance_Float, &vrSettingsError);
	if (vrSettingsError != vr::VRSettingsError_None) {
		LOG(WARNING) << "Could not remove \"" << vr::k_pch_CollisionBounds_FadeDistance_Float << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
	}

	vr::VRSettings()->RemoveKeyInSection(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_CenterMarkerOn_Bool, &vrSettingsError);
	if (vrSettingsError != vr::VRSettingsError_None) {
		LOG(WARNING) << "Could not remove \"" << vr::k_pch_CollisionBounds_CenterMarkerOn_Bool << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
	}

	vr::VRSettings()->RemoveKeyInSection(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_PlaySpaceOn_Bool, &vrSettingsError);
	if (vrSettingsError != vr::VRSettingsError_None) {
		LOG(WARNING) << "Could not remove \"" << vr::k_pch_CollisionBounds_PlaySpaceOn_Bool << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
	}

	setForceBounds(false);

	vr::VRSettings()->Sync();
	settingsUpdateCounter = 999; // Easiest way to get default values
}


void ChaperoneTabController::shutdown() {
	if (m_enableChaperoneSwitchToBeginner && m_chaperoneSwitchToBeginnerActive) {
		vr::EVRSettingsError vrSettingsError;
		vr::VRSettings()->SetInt32(vr::k_pch_CollisionBounds_Section, vr::k_pch_CollisionBounds_Style_Int32, m_chaperoneSwitchToBeginnerLastStyle, &vrSettingsError);
		if (vrSettingsError != vr::VRSettingsError_None) {
			LOG(WARNING) << "Could not set \"" << vr::k_pch_CollisionBounds_Style_Int32 << "\" setting: " << vr::VRSettings()->GetSettingsErrorNameFromEnum(vrSettingsError);
		} else {
			vr::VRSettings()->Sync(true);
		}
	}
}


} // namespace advconfig
